package ch.areion.areionapp.communication;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import android.util.Log;

import ch.areion.common.message.Message;
import ch.areion.common.message.Module;
import ch.areion.common.message.Type;
import ch.areion.common.message.Version;

/**
 * Class which provides the central monitoring functionality.
 * @author leupibr
 * @version 1.0
 */
public class Monitor extends Thread {
	private static String ServerAddr = "192.168.43.203";
	private final static int Port = 4444;
	private final static int FlushCycle = 100;

	/**
	 * Singleton instance variable.
	 */
	private static Monitor instance;

	/**
	 * The Message queue.
	 */
	private ArrayList<Message> msgBuffer;

	/**
	 * The client socket.
	 */
	private Socket cli;

	/**
	 * The output buffer.
	 */
	private PrintWriter out;

	/**
	 * Private Constructor for Singleton implementation.
	 * @author Bruno Leupi
	 */
	private Monitor() {
		msgBuffer = new ArrayList<Message>();
	}

	/**
	 * (Creates) and gets the Monitor instance.
	 * @return The Monitor instance
	 */
	public static Monitor getInstance() {
		if(Monitor.instance == null) {
			Monitor.instance = new Monitor();
			Monitor.instance.start();
		}

		return Monitor.instance;
	}

	/**
	 * Sends all messages.
	 */
	public synchronized void flush() {
		if(out != null && cli.isConnected()) {
			while(msgBuffer.size() > 0) {
				out.println(msgBuffer.remove(0).toString());
			}
			out.flush();
		}
		else {
			while(msgBuffer.size() > 0) {
				Log.d("Areion", msgBuffer.remove(0).toString());
			}
		}
	}

	/**
	 * Adds a given message to the message buffer.
	 * @param msg The given message
	 */
	public synchronized void add(Message msg) {
		this.msgBuffer.add(msg);
	}

	/**
	 * Adds a new message to the message buffer.
	 * @param v The Version of the message
	 * @param t The Type of the message
	 * @param m The Module of the message
	 * @param data The data to send
	 */
	public synchronized void add(Version v, Type t, Module m, String data) {
		this.msgBuffer.add(new Message(v, t, m, data.trim()));
	}

	public synchronized void add(Version v, Type t, Module m, String item,
	    String data) {
		this.msgBuffer.add(new Message(v, t, m, item.trim() + ":" + data.trim()));
	}

	/**
	 * Starts the socket and initializes flush timer.
	 */
	@Override
	public void run() {
		// start connection
		connect(ServerAddr);
		initFlushCycle();
	}

	/**
	 * Try's to connect to server.
	 * @param host The host to connect to.
	 */
	private void connect(String host) {
		try {
			Log.d("Areion", "Opening connection to " + host + ":" + Port);
			this.cli = new Socket(host, Port);
			Log.d("Areion", "Opening stream");
			this.out = new PrintWriter(cli.getOutputStream());
		}
		catch(Exception e) {
			Log.e("Areion",
			    "Unable to init monitoring connection..." + e);
			disconnect();
		}
	}

	private void disconnect() {
		if(this.out != null) {
			this.out.close();
			this.out = null;
		}

		if(this.cli != null) {
			try {
				this.cli.close();
			}
			catch(IOException e) {
				Log.e("Areion",
				    "Unable to close monitoring connection..." + e.getMessage());
			}
			this.cli = null;
		}
	}

	/**
	 * Initializes the flush cycle timer.
	 */
	private void initFlushCycle() {
		Timer myTimer = new Timer();
		myTimer.schedule(new TimerTask() {
			@Override
			public void run() {
				flush();
			}
		}, 0, FlushCycle);

	}

	/**
	 * Returns the host address.
	 * @return Address/IP of the host.
	 */
	public static String getHost() {
		return Monitor.ServerAddr;
	}

	/**
	 * Sets the host address.
	 * @param host
	 */
	public static void setHost(String host) {
		Monitor.ServerAddr = host;
		Monitor.getInstance().disconnect();
		Monitor.instance = null;
		Monitor.getInstance();
	}
}
